package cn.gailvlun.gll.chatlib;

import android.support.annotation.IntDef;

import org.greenrobot.eventbus.EventBus;

import java.io.IOException;
import java.net.SocketException;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import cn.gailvlun.gll.chatlib.bean.ChatFeedback;
import cn.gailvlun.gll.chatlib.bean.ChatRes;
import cn.gailvlun.gll.chatlib.bean.ChatResType;
import cn.gailvlun.gll.chatlib.bean.CrossMessage;
import cn.gailvlun.gll.chatlib.event.ChatStatusEvent;
import cn.gailvlun.gll.chatlib.message.Message;
import cn.gailvlun.gll.chatlib.message.MessageType;
import cn.gailvlun.gll.event.AuthEvent;
import cn.gailvlun.gll.net.GsonUtil;
import cn.gailvlun.gll.net.HttpMethods;
import cn.gailvlun.gll.net.base.Profile;
import cn.gailvlun.gll.net.base.RefreshTokenReq;
import cn.gailvlun.gll.net.base.RefreshTokenRes;
import cn.gailvlun.gll.util.LogUtil;
import cn.gailvlun.gll.util.ProfileUtil;
import cn.gailvlun.gll.util.RxUtil;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okhttp3.logging.HttpLoggingInterceptor;
import okio.ByteString;


public class ChatClient2 {
    private static ChatClient2 INSTANCE = null;

    private ChatClient2() {
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        mClient = new OkHttpClient.Builder()
                .addInterceptor(loggingInterceptor)
                .build();
    }

    public static ChatClient2 getInstance() {
        if (INSTANCE == null) {
            synchronized (ChatClient2.class) {
                if (INSTANCE == null) {
                    INSTANCE = new ChatClient2();
                }
            }
        }
        return INSTANCE;
    }

    private String mUrl;
    private int mUserId;
    private String mToken;
    private OkHttpClient mClient;
    private Request mRequest;
    private WebSocket mWebSocket;

//    private AtomicInteger msgSequenceCounter = new AtomicInteger();

    public static final int NOT_CONNECTED = 1;
    public static final int CONNECTING = 2;
    public static final int CONNECTED = 3;

    @IntDef({NOT_CONNECTED, CONNECTING, CONNECTED})
    public @interface Status {
    }

    public void connect() {
        if (mStatus == NOT_CONNECTED) {
            mUrl = BuildConfig.WEBSOCKET_URL;
            mUserId = ProfileUtil.getUserId();
            mToken = ProfileUtil.getToken();


            Schedulers.io().createWorker()
                    .schedule(new Runnable() {
                        @Override
                        public void run() {
                            disconnect();
                            constructRequest(false);
                            connectWebSocket();
                        }
                    });
        }
    }


    public void disconnect() {
        if (mStatus == CONNECTED) {
            if (mWebSocket != null) {
                synchronized (ChatClient2.class) {
                    if (mWebSocket != null) {
                        setStatus(NOT_CONNECTED);
                        mWebSocket.cancel();
                        mWebSocket = null;
                    }
                }
            }
        }
    }

    private boolean syncRefreshToken() {
        Profile profile = ProfileUtil.getProfile();
        if (profile == null) {
            return false;
        }
        String clientId = profile.getUid() == null ? "+" + profile.getCountry_code() + profile.getMobile()
                : profile.getUid();
        try {
            retrofit2.Response<RefreshTokenRes> res = HttpMethods.getBaseService()
                    .refreshTokenSync(new RefreshTokenReq(clientId, profile.getRefresh_token()))
                    .execute();
            if (res.code() == 200) {
                RefreshTokenRes refreshTokenRes = res.body();
                ProfileUtil.refresh(refreshTokenRes);
                mToken = refreshTokenRes.getToken();
            } else {
                EventBus.getDefault().post(new AuthEvent());
                return false;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }


    private void constructRequest(boolean isAuthFailed) {
        if (isAuthFailed) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (!syncRefreshToken()) {
                return;
            }
        }
        mRequest = new Request.Builder()
                .url(mUrl + "ws?id=" + mUserId + "&token=" + mToken)
                .build();
    }

    private void connectWebSocket() {
        setStatus(CONNECTING);
        mWebSocket = mClient.newWebSocket(mRequest, new WebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                super.onOpen(webSocket, response);
                retryCount = 0;
                setStatus(CONNECTED);

//                msgSequenceCounter.set(Integer.valueOf(response.header("msg_count", "0")));
            }

            @Override
            public void onMessage(WebSocket webSocket, String text) {
                super.onMessage(webSocket, text);
                LogUtil.d(text);

                ChatRes chatRes = ChatUtil.getChatRes(text);

                ChatUtil.processMessage(chatRes);

                if (chatRes.getMsg_type() != ChatResType.ark) {
                    ChatFeedback chatFeedback = new ChatFeedback(chatRes.getMsg().getId(), chatRes.getSeq());
                    String feedback = GsonUtil.getGson().toJson(chatFeedback);
                    LogUtil.d(feedback);
                    webSocket.send(feedback);

                    if (ChatUtil.isXRoomMsg(chatRes)) {
                        String uuid = ChatUtil.getXRoomId(chatRes);
                        ChatFeedback xroomFeedback = new ChatFeedback(uuid);
                        webSocket.send(GsonUtil.getGson().toJson(xroomFeedback));
                    }
                }
            }

            @Override
            public void onMessage(WebSocket webSocket, ByteString bytes) {
                super.onMessage(webSocket, bytes);
                LogUtil.d(bytes.string(Charset.defaultCharset()));
            }

            @Override
            public void onClosing(WebSocket webSocket, int code, String reason) {
                super.onClosing(webSocket, code, reason);
                LogUtil.d("onClosing: code:" + code + ", reason: " + reason);

                setStatus(CONNECTING);
                retry(code == 4001);
            }

            @Override
            public void onClosed(WebSocket webSocket, int code, String reason) {
                super.onClosed(webSocket, code, reason);

                LogUtil.d("onClosed: code:" + code + ", reason:" + reason);
            }

            @Override
            public void onFailure(WebSocket webSocket, Throwable t, Response response) {
                super.onFailure(webSocket, t, response);

                LogUtil.d("onFailure");
                if (response != null && response.body() != null) {
                    try {
                        LogUtil.d(response.body().string());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                t.printStackTrace();
                setStatus(NOT_CONNECTED);

                if (!(t instanceof SocketException)) {

                    RxUtil.timer(RETRY_INTERVAL, TimeUnit.MILLISECONDS)
                            .subscribe(new Consumer<Long>() {
                                @Override
                                public void accept(Long aLong) throws Exception {
                                    retry(false);
                                }
                            });
                }
            }
        });
    }

    @Status
    private int mStatus = NOT_CONNECTED;

    private void setStatus(@Status int status) {
        mStatus = status;
        EventBus.getDefault().post(new ChatStatusEvent(status));
    }

    @Status
    public int getStatus() {
        return mStatus;
    }

    private int retryCount;
    private int RETRY_COUNT = 3;
    private int RETRY_INTERVAL = 5000;

    public void retry() {
        updateToken();
        disconnect();
        retry(false);
    }

    private void updateToken() {
        mToken = ProfileUtil.getToken();
    }

    private void retry(final boolean authFailed) {
        Schedulers.io().createWorker()
                .schedule(new Runnable() {
                    @Override
                    public void run() {
                        if (retryCount > RETRY_COUNT) {

                            setStatus(NOT_CONNECTED);
                            return;
                        }
                        LogUtil.d("重连" + retryCount + "次");
                        retryInternal(authFailed);
                        retryCount++;
                    }
                });
    }

    private void retryInternal(boolean isAuthFailed) {
        disconnect();
        constructRequest(isAuthFailed);
        connectWebSocket();
    }

    public boolean sendMessage(Message msg) {
        if (mWebSocket == null) {
            return false;
        }
        return mWebSocket.send(ChatUtil.sendMessage(msg));
    }

    public boolean sendCrossMessage(CrossMessage msg) {
        if (mWebSocket == null) {
            return false;
        }
        return mWebSocket.send(ChatUtil.sendCrossMessage(msg));
    }
}
